﻿// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Xml;
using Emotive.Otf.OtxEditor.Dom;
using System.Linq;
using System.Collections;
using Emotive.Otf.OtxEditor.Document;
using ICSharpCode.NRefactory;
using Emotive.Otf.OtxEditor.Editor;
using System.Xml.Linq;
using System.Text;
using System.IO;
using Emotive.Otf.OtxEditor.Presentation;
using System.Windows.Forms;
using System.Collections.ObjectModel;
using Emotive.Otf.OtxEditor.Util;
using System.Windows.Markup;

namespace Emotive.Otf.OtxEditor.Utils
{
	static class ExtensionMethods
	{
		#region Epsilon / IsClose / CoerceValue
		/// <summary>
		/// Epsilon used for <c>IsClose()</c> implementations.
		/// We can use up quite a few digits in front of the decimal point (due to visual positions being relative to document origin),
		/// and there's no need to be too accurate (we're dealing with pixels here),
		/// so we will use the value 0.01.
		/// Previosly we used 1e-8 but that was causing issues:
		/// http://community.sharpdevelop.net/forums/t/16048.aspx
		/// </summary>
		public const double Epsilon = 0.01;
		
		/// <summary>
		/// Returns true if the doubles are close (difference smaller than 0.01).
		/// </summary>
		public static bool IsClose(this double d1, double d2)
		{
			if (d1 == d2) // required for infinities
				return true;
			return Math.Abs(d1 - d2) < Epsilon;
		}
		
		/// <summary>
		/// Returns true if the doubles are close (difference smaller than 0.01).
		/// </summary>
		public static bool IsClose(this Size d1, Size d2)
		{
			return IsClose(d1.Width, d2.Width) && IsClose(d1.Height, d2.Height);
		}
		
		/// <summary>
		/// Returns true if the doubles are close (difference smaller than 0.01).
		/// </summary>
		public static bool IsClose(this Vector d1, Vector d2)
		{
			return IsClose(d1.X, d2.X) && IsClose(d1.Y, d2.Y);
		}
		
		/// <summary>
		/// Forces the value to stay between mininum and maximum.
		/// </summary>
		/// <returns>minimum, if value is less than minimum.
		/// Maximum, if value is greater than maximum.
		/// Otherwise, value.</returns>
		public static double CoerceValue(this double value, double minimum, double maximum)
		{
			return Math.Max(Math.Min(value, maximum), minimum);
		}
		
		/// <summary>
		/// Forces the value to stay between mininum and maximum.
		/// </summary>
		/// <returns>minimum, if value is less than minimum.
		/// Maximum, if value is greater than maximum.
		/// Otherwise, value.</returns>
		public static int CoerceValue(this int value, int minimum, int maximum)
		{
			return Math.Max(Math.Min(value, maximum), minimum);
		}
		#endregion
		
		#region CreateTypeface
		/// <summary>
		/// Creates typeface from the framework element.
		/// </summary>
		public static Typeface CreateTypeface(this FrameworkElement fe)
		{
			return new Typeface((FontFamily)fe.GetValue(TextBlock.FontFamilyProperty),
			                    (FontStyle)fe.GetValue(TextBlock.FontStyleProperty),
			                    (FontWeight)fe.GetValue(TextBlock.FontWeightProperty),
			                    (FontStretch)fe.GetValue(TextBlock.FontStretchProperty));
		}
		#endregion
		
		#region AddRange / Sequence
		public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> elements)
		{
			foreach (T e in elements)
				collection.Add(e);
		}
		
		/// <summary>
		/// Creates an IEnumerable with a single value.
		/// </summary>
		public static IEnumerable<T> Sequence<T>(T value)
		{
			yield return value;
		}
		#endregion
		
		#region XML reading
		/// <summary>
		/// Gets the value of the attribute, or null if the attribute does not exist.
		/// </summary>
		public static string GetAttributeOrNull(this XmlElement element, string attributeName)
		{
			XmlAttribute attr = element.GetAttributeNode(attributeName);
			return attr != null ? attr.Value : null;
		}
		
		/// <summary>
		/// Gets the value of the attribute as boolean, or null if the attribute does not exist.
		/// </summary>
		public static bool? GetBoolAttribute(this XmlElement element, string attributeName)
		{
			XmlAttribute attr = element.GetAttributeNode(attributeName);
			return attr != null ? (bool?)XmlConvert.ToBoolean(attr.Value) : null;
		}
		
		/// <summary>
		/// Gets the value of the attribute as boolean, or null if the attribute does not exist.
		/// </summary>
		public static bool? GetBoolAttribute(this XmlReader reader, string attributeName)
		{
			string attributeValue = reader.GetAttribute(attributeName);
			if (attributeValue == null)
				return null;
			else
				return XmlConvert.ToBoolean(attributeValue);
		}
		#endregion
		
		#region DPI independence
		public static Rect TransformToDevice(this Rect rect, Visual visual)
		{
			Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice;
			return Rect.Transform(rect, matrix);
		}
		
		public static Rect TransformFromDevice(this Rect rect, Visual visual)
		{
			Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice;
			return Rect.Transform(rect, matrix);
		}
		
		public static Size TransformToDevice(this Size size, Visual visual)
		{
			Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice;
			return new Size(size.Width * matrix.M11, size.Height * matrix.M22);
		}
		
		public static Size TransformFromDevice(this Size size, Visual visual)
		{
			Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice;
			return new Size(size.Width * matrix.M11, size.Height * matrix.M22);
		}
		
		public static Point TransformToDevice(this Point point, Visual visual)
		{
			Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice;
			return new Point(point.X * matrix.M11, point.Y * matrix.M22);
		}
		
		public static Point TransformFromDevice(this Point point, Visual visual)
		{
			Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice;
			return new Point(point.X * matrix.M11, point.Y * matrix.M22);
		}
		#endregion
		
		#region System.Drawing <-> WPF conversions
        //public static System.Drawing.Point ToSystemDrawing(this Point p)
        //{
        //    return new System.Drawing.Point((int)p.X, (int)p.Y);
        //}
		
        //public static Point ToWpf(this System.Drawing.Point p)
        //{
        //    return new Point(p.X, p.Y);
        //}
		
        //public static Size ToWpf(this System.Drawing.Size s)
        //{
        //    return new Size(s.Width, s.Height);
        //}
		
		public static Rect ToWpf(this System.Drawing.Rectangle rect)
		{
			return new Rect(rect.Location.ToWpf(), rect.Size.ToWpf());
		}
		#endregion
		
		[Conditional("DEBUG")]
		public static void CheckIsFrozen(Freezable f)
		{
			if (f != null && !f.IsFrozen)
				Debug.WriteLine("Performance warning: Not frozen: " + f.ToString());
		}
		
		[Conditional("DEBUG")]
		public static void Log(bool condition, string format, params object[] args)
		{
			if (condition) {
				string output = DateTime.Now.ToString("hh:MM:ss") + ": " + string.Format(format, args) + Environment.NewLine + Environment.StackTrace;
				Console.WriteLine(output);
				Debug.WriteLine(output);
			}
        }

        #region Dom


        public static void AddRange(this ArrayList arrayList, IEnumerable elements)
		{
			foreach (object o in elements)
				arrayList.Add(o);
		}
		
        //public static void AddRange<T>(this ICollection<T> list, IEnumerable<T> elements)
        //{
        //    foreach (T o in elements)
        //        list.Add(o);
        //}
		
		/// <summary>
		/// Converts a recursive data structure into a flat list.
		/// </summary>
		/// <param name="input">The root elements of the recursive data structure.</param>
		/// <param name="recursive">The function that gets the children of an element.</param>
		/// <returns>Iterator that enumerates the tree structure in preorder.</returns>
        //public static IEnumerable<T> Flatten<T>(this IEnumerable<T> input, Func<T, IEnumerable<T>> recursion)
        //{
        //    Stack<IEnumerator<T>> stack = new Stack<IEnumerator<T>>();
        //    try {
        //        stack.Push(input.GetEnumerator());
        //        while (stack.Count > 0) {
        //            while (stack.Peek().MoveNext()) {
        //                T element = stack.Peek().Current;
        //                yield return element;
        //                IEnumerable<T> children = recursion(element);
        //                if (children != null) {
        //                    stack.Push(children.GetEnumerator());
        //                }
        //            }
        //            stack.Pop().Dispose();
        //        }
        //    } finally {
        //        while (stack.Count > 0) {
        //            stack.Pop().Dispose();
        //        }
        //    }
        //}
		
		public static IEnumerable<IUsing> GetAllUsings(this ICompilationUnit cu)
		{
			return (new[]{cu.UsingScope}).Flatten(s=>s.ChildScopes).SelectMany(s=>s.Usings);
		}
	
        #endregion


        /// <summary>
        /// Raises the event.
        /// Does nothing if eventHandler is null.
        /// Because the event handler is passed as parameter, it is only fetched from the event field one time.
        /// This makes
        /// <code>MyEvent.RaiseEvent(x,y);</code>
        /// thread-safe
        /// whereas
        /// <code>if (MyEvent != null) MyEvent(x,y);</code>
        /// would not be safe.
        /// </summary>
        /// <remarks>Using this method is only thread-safe under the Microsoft .NET memory model,
        /// not under the less strict memory model in the CLI specification.</remarks>
        public static void RaiseEvent(this EventHandler eventHandler, object sender, EventArgs e)
        {
            if (eventHandler != null)
            {
                eventHandler(sender, e);
            }
        }

        /// <summary>
        /// Raises the event.
        /// Does nothing if eventHandler is null.
        /// Because the event handler is passed as parameter, it is only fetched from the event field one time.
        /// This makes
        /// <code>MyEvent.RaiseEvent(x,y);</code>
        /// thread-safe
        /// whereas
        /// <code>if (MyEvent != null) MyEvent(x,y);</code>
        /// would not be safe.
        /// </summary>
        public static void RaiseEvent<T>(this EventHandler<T> eventHandler, object sender, T e) where T : EventArgs
        {
            if (eventHandler != null)
            {
                eventHandler(sender, e);
            }
        }

        /// <summary>
        /// Runs an action for all elements in the input.
        /// </summary>
        public static void ForEach<T>(this IEnumerable<T> input, Action<T> action)
        {
            if (input == null)
                throw new ArgumentNullException("input");
            foreach (T element in input)
            {
                action(element);
            }
        }

        // <summary>
        // Adds all <paramref name="elements"/> to <paramref name="list"/>.
        // </summary>
        //public static void AddRange<T>(this ICollection<T> list, IEnumerable<T> elements)
        //{
        //    foreach (T o in elements)
        //        list.Add(o);
        //}

        public static void AddRange(this IList arrayList, IEnumerable elements)
        {
            foreach (object o in elements)
                arrayList.Add(o);
        }

        public static ReadOnlyCollection<T> AsReadOnly<T>(this IList<T> arr)
        {
            return new ReadOnlyCollection<T>(arr);
        }

        public static ReadOnlyCollectionWrapper<T> AsReadOnly<T>(this ICollection<T> arr)
        {
            return new ReadOnlyCollectionWrapper<T>(arr);
        }

        public static IEnumerable<System.Windows.Forms.Control> GetRecursive(this System.Windows.Forms.Form.ControlCollection collection)
        {
            return collection.Cast<System.Windows.Forms.Control>().Flatten(c => c.Controls.Cast<System.Windows.Forms.Control>());
        }

        /// <summary>
        /// Converts a recursive data structure into a flat list.
        /// </summary>
        /// <param name="input">The root elements of the recursive data structure.</param>
        /// <param name="recursion">The function that gets the children of an element.</param>
        /// <returns>Iterator that enumerates the tree structure in preorder.</returns>
        public static IEnumerable<T> Flatten<T>(this IEnumerable<T> input, Func<T, IEnumerable<T>> recursion)
        {
            Stack<IEnumerator<T>> stack = new Stack<IEnumerator<T>>();
            try
            {
                stack.Push(input.GetEnumerator());
                while (stack.Count > 0)
                {
                    while (stack.Peek().MoveNext())
                    {
                        T element = stack.Peek().Current;
                        yield return element;
                        IEnumerable<T> children = recursion(element);
                        if (children != null)
                        {
                            stack.Push(children.GetEnumerator());
                        }
                    }
                    stack.Pop().Dispose();
                }
            }
            finally
            {
                while (stack.Count > 0)
                {
                    stack.Pop().Dispose();
                }
            }
        }

        /// <summary>
        /// Creates an array containing a part of the array (similar to string.Substring).
        /// </summary>
        public static T[] Splice<T>(this T[] array, int startIndex)
        {
            if (array == null)
                throw new ArgumentNullException("array");
            return Splice(array, startIndex, array.Length - startIndex);
        }

        /// <summary>
        /// Creates an array containing a part of the array (similar to string.Substring).
        /// </summary>
        public static T[] Splice<T>(this T[] array, int startIndex, int length)
        {
            if (array == null)
                throw new ArgumentNullException("array");
            if (startIndex < 0 || startIndex > array.Length)
                throw new ArgumentOutOfRangeException("startIndex", startIndex, "Value must be between 0 and " + array.Length);
            if (length < 0 || length > array.Length - startIndex)
                throw new ArgumentOutOfRangeException("length", length, "Value must be between 0 and " + (array.Length - startIndex));
            T[] result = new T[length];
            Array.Copy(array, startIndex, result, 0, length);
            return result;
        }

        public static IEnumerable<T> DistinctBy<T, K>(this IEnumerable<T> input, Func<T, K> keySelector)
        {
            return input.Distinct(KeyComparer.Create(keySelector));
        }

        /// <summary>
        /// Sets the Content property of the specified ControlControl to the specified content.
        /// If the content is a Windows-Forms control, it is wrapped in a WindowsFormsHost.
        /// If the content control already contains a WindowsFormsHost with that content,
        /// the old WindowsFormsHost is kept.
        /// When a WindowsFormsHost is replaced with another content, the host is disposed (but the control
        /// inside the host isn't)
        /// </summary>
        public static void SetContent(this ContentControl contentControl, object content)
        {
            SetContent(contentControl, content, null);
        }

        public static void SetContent(this ContentPresenter contentControl, object content)
        {
            SetContent(contentControl, content, null);
        }

        public static void SetContent(this ContentControl contentControl, object content, object serviceObject)
        {
            if (contentControl == null)
                throw new ArgumentNullException("contentControl");
            // serviceObject = object implementing the old clipboard/undo interfaces
            // to allow WinForms AddIns to handle WPF commands

            //var host = contentControl.Content as SDWindowsFormsHost;
            //if (host != null) {
            //    if (host.Child == content) {
            //        host.ServiceObject = serviceObject;
            //        return;
            //    }
            //    host.Dispose();
            //}
            //if (content is WinForms.Control) {
            //    contentControl.Content = new SDWindowsFormsHost {
            //        Child = (WinForms.Control)content,
            //        ServiceObject = serviceObject,
            //        DisposeChild = false
            //    };
            //} else if (content is string) {
            //    contentControl.Content = new TextBlock {
            //        Text = content.ToString(),
            //        TextWrapping = TextWrapping.Wrap
            //    };
            //} else {
            contentControl.Content = content;
            //}
        }


        public static void SetContent(this ContentPresenter contentControl, object content, object serviceObject)
        {
            if (contentControl == null)
                throw new ArgumentNullException("contentControl");
            // serviceObject = object implementing the old clipboard/undo interfaces
            // to allow WinForms AddIns to handle WPF commands

            //var host = contentControl.Content as SDWindowsFormsHost;
            //if (host != null) {
            //    if (host.Child == content) {
            //        host.ServiceObject = serviceObject;
            //        return;
            //    }
            //    host.Dispose();
            //}
            //if (content is WinForms.Control) {
            //    contentControl.Content = new SDWindowsFormsHost {
            //        Child = (WinForms.Control)content,
            //        ServiceObject = serviceObject,
            //        DisposeChild = false
            //    };
            //} else if (content is string) {
            //    contentControl.Content = new TextBlock {
            //        Text = content.ToString(),
            //        TextWrapping = TextWrapping.Wrap
            //    };
            //} else {
            contentControl.Content = content;
            //}
        }

        #region System.Drawing <-> WPF conversions
        public static System.Drawing.Point ToSystemDrawing(this Point p)
        {
            return new System.Drawing.Point((int)p.X, (int)p.Y);
        }

        public static System.Drawing.Size ToSystemDrawing(this Size s)
        {
            return new System.Drawing.Size((int)s.Width, (int)s.Height);
        }

        public static System.Drawing.Rectangle ToSystemDrawing(this Rect r)
        {
            return new System.Drawing.Rectangle(r.TopLeft.ToSystemDrawing(), r.Size.ToSystemDrawing());
        }

        public static System.Drawing.Color ToSystemDrawing(this System.Windows.Media.Color c)
        {
            return System.Drawing.Color.FromArgb(c.A, c.R, c.G, c.B);
        }

        public static Point ToWpf(this System.Drawing.Point p)
        {
            return new Point(p.X, p.Y);
        }

        public static Size ToWpf(this System.Drawing.Size s)
        {
            return new Size(s.Width, s.Height);
        }

        //public static Rect ToWpf(this System.Drawing.Rectangle rect)
        //{
        //    return new Rect(rect.Location.ToWpf(), rect.Size.ToWpf());
        //}

        public static System.Windows.Media.Color ToWpf(this System.Drawing.Color c)
        {
            return System.Windows.Media.Color.FromArgb(c.A, c.R, c.G, c.B);
        }
        #endregion

        //#region DPI independence
        //public static Rect TransformToDevice(this Rect rect, Visual visual)
        //{
        //    Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice;
        //    return Rect.Transform(rect, matrix);
        //}

        //public static Rect TransformFromDevice(this Rect rect, Visual visual)
        //{
        //    Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice;
        //    return Rect.Transform(rect, matrix);
        //}

        //public static Size TransformToDevice(this Size size, Visual visual)
        //{
        //    Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice;
        //    return new Size(size.Width * matrix.M11, size.Height * matrix.M22);
        //}

        //public static Size TransformFromDevice(this Size size, Visual visual)
        //{
        //    Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice;
        //    return new Size(size.Width * matrix.M11, size.Height * matrix.M22);
        //}

        //public static Point TransformToDevice(this Point point, Visual visual)
        //{
        //    Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice;
        //    return matrix.Transform(point);
        //}

        //public static Point TransformFromDevice(this Point point, Visual visual)
        //{
        //    Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice;
        //    return matrix.Transform(point);
        //}
        //#endregion

        /// <summary>
        /// Removes <param name="stringToRemove" /> from the start of this string.
        /// Throws ArgumentException if this string does not start with <param name="stringToRemove" />.
        /// </summary>
        public static string RemoveFromStart(this string s, string stringToRemove)
        {
            if (s == null)
                return null;
            if (string.IsNullOrEmpty(stringToRemove))
                return s;
            if (!s.StartsWith(stringToRemove))
                throw new ArgumentException(string.Format("{0} does not start with {1}", s, stringToRemove));
            return s.Substring(stringToRemove.Length);
        }

        /// <summary>
        /// Removes <paramref name="stringToRemove" /> from the end of this string.
        /// Throws ArgumentException if this string does not end with <paramref name="stringToRemove" />.
        /// </summary>
        public static string RemoveFromEnd(this string s, string stringToRemove)
        {
            if (s == null) return null;
            if (string.IsNullOrEmpty(stringToRemove))
                return s;
            if (!s.EndsWith(stringToRemove))
                throw new ArgumentException(string.Format("{0} does not end with {1}", s, stringToRemove));
            return s.Substring(0, s.Length - stringToRemove.Length);
        }

        /// <summary>
        /// Trims the string from the first occurence of <paramref name="cutoffStart" /> to the end, including <paramref name="cutoffStart" />.
        /// If the string does not contain <paramref name="cutoffStart" />, just returns the original string.
        /// </summary>
        public static string CutoffEnd(this string s, string cutoffStart)
        {
            if (s == null) return null;
            int pos = s.IndexOf(cutoffStart);
            if (pos != -1)
            {
                return s.Substring(0, pos);
            }
            else
            {
                return s;
            }
        }

        /// <summary>
        /// Takes at most <param name="length" /> first characters from string.
        /// String can be null.
        /// </summary>
        public static string TakeStart(this string s, int length)
        {
            if (string.IsNullOrEmpty(s) || length >= s.Length)
                return s;
            return s.Substring(0, length);
        }

        /// <summary>
        /// Takes at most <param name="length" /> first characters from string, and appends '...' if string is longer.
        /// String can be null.
        /// </summary>
        public static string TakeStartEllipsis(this string s, int length)
        {
            if (string.IsNullOrEmpty(s) || length >= s.Length)
                return s;
            return s.Substring(0, length) + "...";
        }


        public static string Replace(this string original, string pattern, string replacement, StringComparison comparisonType)
        {
            if (original == null)
                throw new ArgumentNullException("original");
            if (pattern == null)
                throw new ArgumentNullException("pattern");
            if (pattern.Length == 0)
                throw new ArgumentException("String cannot be of zero length.", "pattern");
            if (comparisonType != StringComparison.Ordinal && comparisonType != StringComparison.OrdinalIgnoreCase)
                throw new NotSupportedException("Currently only ordinal comparisons are implemented.");

            StringBuilder result = new StringBuilder(original.Length);
            int currentPos = 0;
            int nextMatch = original.IndexOf(pattern, comparisonType);
            while (nextMatch >= 0)
            {
                result.Append(original, currentPos, nextMatch - currentPos);
                // The following line restricts this method to ordinal comparisons:
                // for non-ordinal comparisons, the match length might be different than the pattern length.
                currentPos = nextMatch + pattern.Length;
                result.Append(replacement);

                nextMatch = original.IndexOf(pattern, currentPos, comparisonType);
            }

            result.Append(original, currentPos, original.Length - currentPos);
            return result.ToString();
        }

        public static byte[] GetBytesWithPreamble(this Encoding encoding, string text)
        {
            byte[] encodedText = encoding.GetBytes(text);
            byte[] bom = encoding.GetPreamble();
            if (bom != null && bom.Length > 0)
            {
                byte[] result = new byte[bom.Length + encodedText.Length];
                bom.CopyTo(result, 0);
                encodedText.CopyTo(result, bom.Length);
                return result;
            }
            else
            {
                return encodedText;
            }
        }

        /// <summary>
        /// Creates a new image for the image source.
        /// </summary>
        public static Image CreateImage(this IImage image)
        {
            if (image == null)
                throw new ArgumentNullException("image");
            return new Image { Source = image.ImageSource };
        }

        /// <summary>
        /// Creates a new image for the image source.
        /// </summary>
        [ObsoleteAttribute("Use layout rounding instead")]
        public static UIElement CreatePixelSnappedImage(this IImage image)
        {
            return CreateImage(image);
        }

        /// <summary>
        /// Translates a WinForms menu to WPF.
        /// </summary>
        public static ICollection TranslateToWpf(this ToolStripItem[] items)
        {
            return items.OfType<ToolStripMenuItem>().Select(item => TranslateMenuItemToWpf(item)).ToList();
        }

        static System.Windows.Controls.MenuItem TranslateMenuItemToWpf(ToolStripMenuItem item)
        {
            var r = new System.Windows.Controls.MenuItem();
            r.Header = WpfMenuService.ConvertLabel(item.Text);
            r.InputGestureText = WpfMenuService.ConvertKeys(item.ShortcutKeys);
            //r.InputGestureText = new KeyGesture(Key.F6).GetDisplayStringForCulture(Thread.CurrentThread.CurrentUICulture);
            if (item.ImageIndex >= 0)
                r.Icon = ClassBrowserIconService.GetImageByIndex(item.ImageIndex).CreateImage();
            if (item.DropDownItems.Count > 0)
            {
                foreach (ToolStripMenuItem subItem in item.DropDownItems)
                {
                    r.Items.Add(TranslateMenuItemToWpf(subItem));
                }
            }
            else
            {
                r.Click += delegate { item.PerformClick(); };
            }
            r.IsChecked = item.Checked;
            return r;
        }

        /// <summary>
        /// Returns the index of the first element for which <paramref name="predicate"/> returns true.
        /// If none of the items in the list fits the <paramref name="predicate"/>, -1 is returned.
        /// </summary>
        public static int FindIndex<T>(this IList<T> list, Func<T, bool> predicate)
        {
            for (int i = 0; i < list.Count; i++)
            {
                if (predicate(list[i]))
                    return i;
            }

            return -1;
        }

        /// <summary>
        /// Adds item to the list if the item is not null.
        /// </summary>
        public static void AddIfNotNull<T>(this IList<T> list, T itemToAdd)
        {
            if (itemToAdd != null)
                list.Add(itemToAdd);
        }

        public static void RemoveWhere<T>(this IList<T> list, Predicate<T> condition)
        {
            if (list == null)
                throw new ArgumentNullException("list");
            int i = 0;
            while (i < list.Count)
            {
                if (condition(list[i]))
                    list.RemoveAt(i);
                else
                    i++;
            }
        }

        public static ExpressionResult FindFullExpressionAtCaret(this ITextEditor editor)
        {
            if (editor == null)
                throw new ArgumentNullException("editor");
            return ParserService.FindFullExpression(editor.Caret.Line, editor.Caret.Column, editor.Document, editor.FileName);
        }

        public static ResolveResult ResolveSymbolAtCaret(this ITextEditor editor)
        {
            if (editor == null)
                throw new ArgumentNullException("editor");
            return ParserService.Resolve(editor.Caret.Line, editor.Caret.Column, editor.Document, editor.FileName);
        }

        public static bool IsEmpty(ExpressionResult expr)
        {
            return expr.Region.IsEmpty;
        }

        public static void WriteTo(this Stream sourceStream, Stream targetStream)
        {
            byte[] buffer = new byte[4096];
            int bytes;
            while ((bytes = sourceStream.Read(buffer, 0, buffer.Length)) > 0)
                targetStream.Write(buffer, 0, bytes);
        }

        public static XElement FormatXml(this XElement element, int indentationLevel)
        {
            StringWriter sw = new StringWriter();
            using (XmlTextWriter xmlW = new XmlTextWriter(sw))
            {
                if (EditorControlService.GlobalOptions.ConvertTabsToSpaces)
                {
                    xmlW.IndentChar = ' ';
                    xmlW.Indentation = EditorControlService.GlobalOptions.IndentationSize;
                }
                else
                {
                    xmlW.Indentation = 1;
                    xmlW.IndentChar = '\t';
                }
                xmlW.Formatting = Formatting.Indented;
                element.WriteTo(xmlW);
            }
            string xmlText = sw.ToString();
            xmlText = xmlText.Replace(sw.NewLine, sw.NewLine + GetIndentation(indentationLevel));
            return XElement.Parse(xmlText, LoadOptions.PreserveWhitespace);
        }

        static string GetIndentation(int level)
        {
            StringBuilder indentation = new StringBuilder();
            for (int i = 0; i < level; i++)
            {
                indentation.Append(EditorControlService.GlobalOptions.IndentationString);
            }
            return indentation.ToString();
        }

        public static XElement AddWithIndentation(this XElement element, XElement newContent)
        {
            int indentationLevel = 0;
            XElement tmp = element;
            while (tmp != null)
            {
                tmp = tmp.Parent;
                indentationLevel++;
            }
            if (!element.Nodes().Any())
            {
                element.Add(new XText(Environment.NewLine + GetIndentation(indentationLevel - 1)));
            }
            XText whitespace = element.Nodes().Last() as XText;
            if (whitespace != null && string.IsNullOrWhiteSpace(whitespace.Value))
            {
                whitespace.AddBeforeSelf(new XText(Environment.NewLine + GetIndentation(indentationLevel)));
                whitespace.AddBeforeSelf(newContent = FormatXml(newContent, indentationLevel));
            }
            else
            {
                element.Add(new XText(Environment.NewLine + GetIndentation(indentationLevel)));
                element.Add(newContent = FormatXml(newContent, indentationLevel));
            }
            return newContent;
        }

        public static XElement AddFirstWithIndentation(this XElement element, XElement newContent)
        {
            int indentationLevel = 0;
            StringBuilder indentation = new StringBuilder();
            XElement tmp = element;
            while (tmp != null)
            {
                tmp = tmp.Parent;
                indentationLevel++;
                indentation.Append(EditorControlService.GlobalOptions.IndentationString);
            }
            if (!element.Nodes().Any())
            {
                element.Add(new XText(Environment.NewLine + GetIndentation(indentationLevel - 1)));
            }
            element.AddFirst(newContent = FormatXml(newContent, indentationLevel));
            element.AddFirst(new XText(Environment.NewLine + indentation.ToString()));
            return newContent;
        }

        public static bool AutoCompleteWithCamelHumpsMatch(this string entityName, string entityPartName)
        {
            string camelHumpsPrefix = new string(entityName.Where<char>(c => Char.IsUpper(c)).Select(c => c).ToArray());
            return entityName.AutoCompleteMatch(entityPartName) || camelHumpsPrefix.AutoCompleteMatch(entityPartName);
        }

        public static bool AutoCompleteMatch(this string entityName, string entityPartName)
        {
            return (entityName.IndexOf(entityPartName, StringComparison.OrdinalIgnoreCase) >= 0);
        }


        #region Dom, AST, Editor, Document
        public static Location GetStart(this DomRegion region)
        {
            return new Location(region.BeginColumn, region.BeginLine);
        }

        public static Location GetEnd(this DomRegion region)
        {
            return new Location(region.EndColumn, region.EndLine);
        }

        public static IEnumerable<IProjectContent> ThreadSafeGetReferencedContents(this IProjectContent pc)
        {
            lock (pc.ReferencedContents)
            {
                return pc.ReferencedContents.ToList();
            }
        }

        public static int PositionToOffset(this IDocument document, Location location)
        {
            return document.PositionToOffset(location.Line, location.Column);
        }

        public static string GetText(this IDocument document, Location startPos, Location endPos)
        {
            int startOffset = document.PositionToOffset(startPos);
            return document.GetText(startOffset, document.PositionToOffset(endPos) - startOffset);
        }

        public static void ClearSelection(this ITextEditor editor)
        {
            editor.Select(editor.Document.PositionToOffset(editor.Caret.Position), 0);
        }

        public static Location ToLocation(this TextLocation loc)
        {
            return new Location(loc.Column, loc.Line);
        }

        public static TextLocation ToTextLocation(this Location loc)
        {
            return new TextLocation(loc.Line, loc.Column);
        }
        #endregion

        #region WpfForm

        /// <summary>
        /// Sets the value of a dependency property on <paramref name="targetObject"/> using a markup extension.
        /// </summary>
        /// <remarks>This method does not support markup extensions like x:Static that depend on
        /// having a XAML file as context.</remarks>
        public static void SetValueToExtension(this DependencyObject targetObject, DependencyProperty property, MarkupExtension markupExtension)
        {
            if (targetObject == null)
                throw new ArgumentNullException("targetObject");
            if (property == null)
                throw new ArgumentNullException("property");
            if (markupExtension == null)
                throw new ArgumentNullException("markupExtension");

            var serviceProvider = new SetValueToExtensionServiceProvider(targetObject, property);
            targetObject.SetValue(property, markupExtension.ProvideValue(serviceProvider));
        }

        #endregion
        sealed class SetValueToExtensionServiceProvider : IServiceProvider, IProvideValueTarget
        {
            readonly DependencyObject targetObject;
            readonly DependencyProperty targetProperty;

            public SetValueToExtensionServiceProvider(DependencyObject targetObject, DependencyProperty property)
            {
                this.targetObject = targetObject;
                this.targetProperty = property;
            }

            public object GetService(Type serviceType)
            {
                if (serviceType == typeof(IProvideValueTarget))
                    return this;
                else
                    return null;
            }

            public object TargetObject
            {
                get { return targetObject; }
            }

            public object TargetProperty
            {
                get { return targetProperty; }
            }
        }

    }


    public static class ExtensionMethodsPublic
    {
        // the difference between IClass and IReturnType is that IClass only contains the members
        // that are declared in this very class,
        // and IReturnType contains also members from base classes (including System.Object) and default (undeclared) constructors

        static SignatureComparer memberSignatureComparer = new SignatureComparer();

        public static bool HasMember(this IClass containingClass, IMember member)
        {
            return containingClass.AllMembers.Any(m => memberSignatureComparer.Equals(member, m));
        }

        public static bool HasMember(this IReturnType containingClass, IMember member)
        {
            return containingClass.GetMembers().Any(m => memberSignatureComparer.Equals(member, m));
        }

        public static bool ImplementsInterface(this IClass targetClass, IClass requiredInterface)
        {
            var targetClassType = targetClass.GetCompoundClass().DefaultReturnType;
            var requiredInterfaceType = requiredInterface.GetCompoundClass().DefaultReturnType;
            // class.DefaultReturnType.GetMethods() returns also methods from base classes, default ctor, ToString() etc. etc.
            return !requiredInterfaceType.GetMembers().Any(missingMember => !targetClassType.HasMember(missingMember));
        }

        public static bool ImplementsAbstractClass(this IClass targetClass, IClass abstractClass)
        {
            var requiredAbstractMembers = MemberLookupHelper.GetAccessibleMembers(abstractClass.DefaultReturnType, targetClass, LanguageProperties.CSharp, true).Where(m => m.IsAbstract);
            return !requiredAbstractMembers.Any(missingMember => !targetClass.HasMember(missingMember));
        }

        public static IEnumerable<IMember> GetMembers(this IReturnType typeReference)
        {
            var properties = typeReference.GetProperties().Cast<IMember>();
            var methods = typeReference.GetMethods().Cast<IMember>();
            var fields = typeReference.GetFields().Cast<IMember>();
            var events = typeReference.GetEvents().Cast<IMember>();
            return properties.Concat(methods).Concat(fields).Concat(events);
        }
    }
}
